package de.hsowl.tod4a.global.util;

import java.io.IOException;
import java.io.StringReader;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.log4j.Priority;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;

import de.hsowl.tod4a.global.model.Field;
import de.hsowl.tod4a.global.model.ObjectMap;
import de.hsowl.tod4a.global.variables.XmlTags;
import de.hsowl.tod4a.global.xml.ElementHelper;
import de.hsowl.tod4a.global.xml.SAXBuilderHelper;

/**
 * Maps between ObjectMaps and XML constructs
 * 
 * @author Sebastian Witt
 * 
 */
public class Mapper {

	private Logger logger;

	public Mapper() {
		// default
	}

	public Mapper(Logger logger) {
		this.logger = logger;
	}

	/**
	 * Creates an XML-Element from an ObjectMap
	 * 
	 * @param map
	 * @return
	 */
	@SuppressWarnings("deprecation")
	public Element mapToElement(final ObjectMap map) {
		if (map != null) {
			Element object = new Element(XmlTags.OBJECT);

			String name = map.getName();
			object.setAttribute(XmlTags.TYPE, map.getClassName());
			object.setAttribute(XmlTags.ID, map.getObjectID() + "");
			object.setAttribute(XmlTags.NAME, name);

			for (Field field : map.getFields()) {

				ElementHelper fieldEle = new ElementHelper(new Element(field.name));
				fieldEle.setAttribute(XmlTags.TYPE, field.type);

				if (field.kind.equals(XmlTags.KIND_ARRAY)) {
					fieldEle.setAttribute(XmlTags.VALUE, String.valueOf(field.value));
					fieldEle.setAttribute(XmlTags.KIND, XmlTags.KIND_ARRAY);

					String[] arr = Utils.getArrayContent(field.value);

					for (String val : arr) {
						Element item = new Element(XmlTags.ITEM);
						item.setText(URLEncoder.encode(val));
						fieldEle.addContent(item);
					}

				} else if (field.kind.equals(XmlTags.KIND_PRIMITIVE)) {
					fieldEle.setAttribute(XmlTags.VALUE, String.valueOf(field.value));
					fieldEle.setAttribute(XmlTags.KIND, field.kind);

				} else if (field.kind.equals(XmlTags.KIND_OBJECT_ARRAY)) {
					fieldEle.setAttribute(XmlTags.VALUE, String.valueOf(field.value));
					fieldEle.setAttribute(XmlTags.KIND, XmlTags.KIND_OBJECT_ARRAY);

					String[] arr = (String[]) field.value;

					for (String id : arr) {
						Element item = new Element(XmlTags.ITEM);

						if (map.getRuntimeType(field.name, id) != null) {
							item.setAttribute(XmlTags.RUNTIME_TYPE, map.getRuntimeType(field.name, id));
						}

						item.setText(id);
						fieldEle.addContent(item);
					}
				} else if (field.kind.equals(XmlTags.KIND_OBJECT)) {
					String id = String.valueOf(field.value);
					fieldEle.setAttribute(XmlTags.VALUE, id);
					fieldEle.setAttribute(XmlTags.KIND, field.kind);

					if (map.getRuntimeType(field.name, id) != null) {
						fieldEle.setAttribute(XmlTags.RUNTIME_TYPE, map.getRuntimeType(field.name, id));
					}
				}

				object.addContent(fieldEle.element);
			}

			return object;
		} else {
			log("Map is null", Priority.ERROR);
			return null;
		}
	}

	/**
	 * Logs a message
	 * 
	 * @param msg
	 * @param priority
	 */
	private void log(String msg, Priority priority) {
		log(msg, priority, null);
	}

	/**
	 * Logs a message
	 * 
	 * @param msg
	 * @param priority
	 * @param e
	 */
	private void log(String msg, Priority priority, Throwable e) {
		if (logger != null) {
			logger.log(priority, msg, e);
		}
	}

	/**
	 * Generates a list of Objectmaps from a XML Message
	 * 
	 * @param xmlMessage
	 * @return
	 * @throws JDOMException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws InterruptedException
	 */
	@SuppressWarnings("unchecked")
	public List<ObjectMap> getObjectMap(String xmlMessage) throws JDOMException, IOException, ClassNotFoundException,
			InterruptedException {
		final List<ObjectMap> list = new ArrayList<ObjectMap>();
		// ArrayList<WorkerThread> threads = new ArrayList<WorkerThread>();

		SAXBuilderHelper builder = new SAXBuilderHelper(false);

		Document doc = builder.build(new StringReader(xmlMessage));
		Element xmlRoot = doc.getRootElement();
		if (xmlRoot.getName().equals(XmlTags.STORE) == true || xmlRoot.getName().equals(XmlTags.QUERY_RESULT) == true) {

			for (final Element e : (List<Element>) xmlRoot.getChildren()) {
				parseXml(e, list);
			}
		} else {
			throw new RuntimeException("Wrong format");
		}

		doc = null;
		builder = null;
		xmlMessage = null;
		xmlRoot = null;

		return list;
	}

	/**
	 * Builds ObjectMaps from an XML input and returns the maps in a list
	 * 
	 * @param ele
	 * @param list
	 * @return
	 */
	private List<ObjectMap> parseXml(Element ele, List<ObjectMap> list) {
		ElementHelper e = new ElementHelper(ele);
		// List<ObjectMap> list = new ArrayList<ObjectMap>();
		if (e.getName().equals(XmlTags.OBJECT)) {
			ObjectMap map = new ObjectMap(e.getAttributeValue(XmlTags.TYPE));
			map.setObjectID(e.getAttributeValue(XmlTags.ID));
			map.setName(e.getAttributeValue(XmlTags.NAME));

			for (Object f : e.getChildren()) {
				ElementHelper field = new ElementHelper((Element) f);

				if (field.getAttributeValue(XmlTags.KIND).equals(XmlTags.KIND_PRIMITIVE)) {
					map.addField(field.getName(), field.getAttributeValue(XmlTags.TYPE), field.getAttributeValue(XmlTags.VALUE),
							XmlTags.KIND_PRIMITIVE);
				} else if (field.getAttributeValue(XmlTags.KIND).equals(XmlTags.KIND_ARRAY)) {

					List<Element> items = field.getChildren();
					String[] itemValues = new String[items.size()];
					int i = 0;
					for (Element item_ : items) {
						ElementHelper item = new ElementHelper(item_);
						itemValues[i] = item.getText();
						i++;
					}
					String type = field.getAttributeValue(XmlTags.TYPE);
					map.addField(field.getName(), type, itemValues, field.getAttributeValue(XmlTags.KIND));
				} else if (field.getAttributeValue(XmlTags.KIND).equals(XmlTags.KIND_OBJECT_ARRAY)) {
					List<Element> items = field.getChildren();
					String[] itemValues = new String[items.size()];
					int i = 0;
					for (Element item_ : items) {
						ElementHelper item = new ElementHelper(item_);
						itemValues[i] = item.getText();

						if (item.getAttributeValue(XmlTags.RUNTIME_TYPE) != null) {
							map.addRuntimeType(item.getText(), item.getAttributeValue(XmlTags.RUNTIME_TYPE), field.getName());
						}

						i++;
					}
					String type = field.getAttributeValue(XmlTags.TYPE);
					map.addField(field.getName(), type, itemValues, field.getAttributeValue(XmlTags.KIND));
				} else if (field.getAttributeValue(XmlTags.KIND).equals(XmlTags.KIND_OBJECT)) {
					map.addField(field.getName(), field.getAttributeValue(XmlTags.TYPE), field.getAttributeValue(XmlTags.VALUE),
							XmlTags.KIND_OBJECT);
					if (field.getAttributeValue(XmlTags.RUNTIME_TYPE) != null) {
						map.addRuntimeType(field.getAttributeValue(XmlTags.VALUE), field.getAttributeValue(XmlTags.RUNTIME_TYPE),
								field.getName());
					}
				}
				f = null;
				field = null;
			}
			list.add(map);
		}
		e = null;
		return list;
	}
}
